﻿using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Input;

namespace AccessControl.Wpf.Behaviors
{
	class FloatElementBehavior : Behavior<FrameworkElement>
	{
		#region Fields

		Canvas _canvas;
		Point _ptLocation;
		FrameworkElement _element;
		bool _bIsDragging = false;
		private static readonly Type s_ThisType = typeof(FloatElementBehavior);

		#endregion

		#region Dependency Properties

		public static readonly DependencyProperty AllowFloatProperty = DependencyProperty.Register(
			"AllowFloat",
			typeof(bool),
			s_ThisType,
			new PropertyMetadata(true));

		public static readonly DependencyProperty MoveParentProperty = DependencyProperty.Register(
			"MoveParent",
			typeof(bool),
			s_ThisType,
			new PropertyMetadata(false));

		public static readonly DependencyProperty ValidatePositionsProperty = DependencyProperty.Register(
			"ValidatePositions",
			typeof(bool),
			s_ThisType,
			new PropertyMetadata(true));

		#endregion

		#region Public Properties

		public bool AllowFloat
		{
			get { return (bool)GetValue(AllowFloatProperty); }
			set { SetValue(AllowFloatProperty, value); }
		}

		public bool MoveParent
		{
			get { return (bool)GetValue(MoveParentProperty); }
			set { SetValue(MoveParentProperty, value); }
		}

		public bool ValidatePositions
		{
			get { return (bool)GetValue(ValidatePositionsProperty); }
			set { SetValue(ValidatePositionsProperty, value); }
		}

		#endregion

		#region Overrides

		protected override void OnAttached()
		{
			base.OnAttached();

			/* Get the canvas parent */
			if (!FindCanvasParent())
				return;

			if (MoveParent)
				_element = VisualTreeHelper.GetParent(AssociatedObject) as FrameworkElement;
			else
				_element = AssociatedObject;

			/* Validate the framework element */
			if (_element == null)
				return;


			/* And register to the events */
			_element.MouseLeftButtonDown += (OnAssociatedMouseLeftButtonDown);
			_element.MouseMove +=			(OnAssociatedMouseMove);
			_element.MouseLeftButtonUp +=	(OnAssociatedMouseLeftButtonUp);
			_element.LostMouseCapture +=	(OnAssociatedLostMouseCapture);
			_element.MouseLeave +=			(OnAssociatedMouseLeave);
		}

		protected override void OnDetaching()
		{
			_element.MouseLeftButtonDown -= (OnAssociatedMouseLeftButtonDown);
			_element.MouseMove -=			(OnAssociatedMouseMove);
			_element.MouseLeftButtonUp -=	(OnAssociatedMouseLeftButtonUp);
			_element.LostMouseCapture -=	(OnAssociatedLostMouseCapture);
			_element.MouseLeave -=			(OnAssociatedMouseLeave);

			base.OnDetaching();
		}

		#endregion

		#region Private Members

		bool FindCanvasParent()
		{
			DependencyObject o = AssociatedObject;

			while (o != null)
			{
				if (o is Canvas)
				{
					_canvas = o as Canvas;
					return true;
				}

				o = VisualTreeHelper.GetParent(o);
			}

			return false;
		}

		void OnAssociatedMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
		{
			/* Check if we need to drag at all */
			if (_element.ActualWidth < _canvas.ActualWidth && _element.ActualHeight < _canvas.ActualHeight && ValidatePositions)
				return;

			if (!AllowFloat)
				return;

			_bIsDragging = true;
			_ptLocation = e.GetPosition(_canvas);
		}

		void OnAssociatedMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
		{
			double xDiff, yDiff;
			double left, top;
			Point ptCurrentLocation;

			if (!_bIsDragging)
				return;

			/* Calc the diffs */
			ptCurrentLocation = e.GetPosition(_canvas);
			xDiff = ptCurrentLocation.X - _ptLocation.X;
			yDiff = ptCurrentLocation.Y - _ptLocation.Y;

			/* Get the current values */
			left = (double)_element.GetValue(Canvas.LeftProperty);
			top = (double)_element.GetValue(Canvas.TopProperty);

			/* Adjust */
			left += xDiff;
			top += yDiff;

			/* Validate */
			if (ValidatePositions)
			{
				if (left > 0)
					left = 0;
				
				if ((left + _element.ActualWidth) < _canvas.ActualWidth)
					left = _canvas.ActualWidth - _element.ActualWidth;
				
				if (top > 0)
					top = 0;
				
				if ((top + _element.ActualHeight) < _canvas.ActualHeight)
					top = _canvas.ActualHeight - _element.ActualHeight;
			}

			/* Set the new values */
			_element.SetValue(Canvas.LeftProperty, left);
			_element.SetValue(Canvas.TopProperty, top);

			/* And save the current location */
			_ptLocation = ptCurrentLocation;
		}

		void OnAssociatedMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
		{
			_bIsDragging = false;
		}

		void OnAssociatedLostMouseCapture(object sender, MouseEventArgs e)
		{
			_bIsDragging = false;
		}

		void OnAssociatedMouseLeave(object sender, MouseEventArgs e)
		{
			_bIsDragging = false;
		}

		#endregion
	}
}
